<?php

namespace App\Models\Train\Invite;

use App\Models\Passengers\Passenger;
use App\Models\Passengers\Repositories\Interfaces\PassengerRepositoryInterface;
use App\Trip\Train\TrainInfo\TrainInfo;
use App\Trip\Traits\ModelDateTrait;
use Carbon\Carbon;
use Eloquent;
use Exception;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Validator;

/**
 * App\Models\Train\Invite\Invite
 *
 * @property int                             $invite_id
 *           邀请id
 * @property string                          $user_id
 *           用户ID
 * @property string                          $train_code
 *           火车车次
 * @property string                          $from_station_code
 *           出发站 code
 * @property string                          $to_station_code
 *           目的站 code
 * @property string                          $train_date
 *           出发日期
 * @property \Illuminate\Support\Carbon|null $created_at
 * @property \Illuminate\Support\Carbon|null $updated_at
 * @property-read Collection|InviteDetails[] $joins
 * @property-read int|null                   $joins_count
 * @method static Builder|Invite newModelQuery()
 * @method static Builder|Invite newQuery()
 * @method static Builder|Invite query()
 * @method static Builder|Invite whereCreatedAt($value)
 * @method static Builder|Invite whereFromStationCode($value)
 * @method static Builder|Invite whereInviteId($value)
 * @method static Builder|Invite whereToStationCode($value)
 * @method static Builder|Invite whereTrainCode($value)
 * @method static Builder|Invite whereTrainDate($value)
 * @method static Builder|Invite whereUpdatedAt($value)
 * @method static Builder|Invite whereUserId($value)
 * @mixin Eloquent
 */
class Invite extends Model
{
    use HasFactory, ModelDateTrait;

    // 1 小时过期
    const EXPIRED_TIMES = 3600;
    protected $table      = 'train_trip_invite';
    protected $primaryKey = 'invite_id';
    protected $fillable
                          = [
            'user_id',
            'train_code',
            'from_station_code',
            'to_station_code',
            'train_date',
        ];

    public function joins()
    {
        return $this->hasMany(InviteDetails::class, 'invite_id', 'invite_id');
    }

    /**
     * 参与
     * @throws Exception
     */
    public function participate(Invite $invite_info, $data)
    {

        if ($invite_info->isExpired()) {
            throw new Exception("邀请不存在或已过期", 404);
        }

        $join        = new InviteDetails();
        $join_detail = null;
        DB::transaction(function () use ($join, $invite_info, $data, &$join_detail) {
            $join_detail = $join->participate($invite_info, $data);
            $invite_info->copyToPassenger($join_detail);
        });

        return $join_detail;
    }

    public function isExpired(): bool
    {
        return $this->expired() == 0;
    }

    /**
     * 还有多久过期
     *
     * @return  int
     */
    public function expired(): int
    {
        if (Carbon::parse($this->created_at)->addSeconds(self::EXPIRED_TIMES)->lessThan(Carbon::now())) {
            return 0;
        }

        return Carbon::parse($this->created_at)->addSeconds(self::EXPIRED_TIMES)->diffInSeconds(Carbon::now());
    }

    private function copyToPassenger(InviteDetails $detail)
    {
        /**
         * @var PassengerRepositoryInterface $passengerRepo
         * @var Passenger                    $passenger
         */
        $passengerRepo = app(PassengerRepositoryInterface::class);
        if ($passenger = $passengerRepo->findOneByIdentityCodeAndUserId($detail->getIdentityCode(), $this->inviter_id())) {
            $passengerRepo->updatePassenger($passenger, [
                'user_id'        => $passenger->getUserId(),
                'identity_type'  => $passenger->getIdentityType(),
                'identity_code'  => $passenger->getIdentityCode(),
                'passenger_type' => $passenger->getPassengerType(),
                'truename'       => $detail->getTrueName(),
                'mobile'         => $detail->getMobile(),
            ]);
            return $passenger;
        } else {
            $passengerRepo->addPassenger([
                'user_id'        => $this->inviter_id(),
                'truename'       => $detail->getTrueName(),
                'identity_type'  => $detail->getIdentityType(),
                'identity_code'  => $detail->getIdentityCode(),
                'passenger_type' => $detail->getPassengerType(),
                'mobile'         => $detail->getMobile(),
                'invite_user_id' => $detail->user_id(),
            ]);
        }
    }

    public function inviter_id(): string
    {
        return $this->getUserId();
    }

    public function getUserId(): string
    {
        return $this->getAttribute('user_id');
    }

    public function getInviteInfo($invite_id): ?self
    {
        return $this->find($invite_id);
    }

    public function initInviteInfo(array $data): ?self
    {
        $this->validInitData($data);
        //读取缓存
        $cache_key = "Train:Trip:Invite:{$data['user_id']}:{$data['train_code']}:{$data['from_station_code']}:{$data['to_station_code']}:{$data['train_date']}";
        if (Cache::has($cache_key)) {
            $invite_info = Cache::get($cache_key);
        } else {
            $valid = $this->getValidInviteInfo($data);
            if ($valid) {
                return $valid;
            }
            $invite_info = $this->create($data);
        }

        return $invite_info;
    }

    /**
     * @throws Exception
     */
    private function validInitData(array $data)
    {
        $validator = Validator::make($data, [
            'user_id'           => 'required',
            'train_code'        => 'required',
            'from_station_code' => 'required',
            'to_station_code'   => 'required',
            'train_date'        => 'required|date',
        ]);

        if ($validator->fails()) {
            throw new Exception("参数错误");
        }

        if (Carbon::parse($data['train_date'])->lessThan(Carbon::today())) {
            throw new Exception("行程已过期", 404);
        }
        if (Carbon::hasFormat($data['train_date'], 'Y-m-d H:i:s')) {
            if (Carbon::parse($data['train_date'])->lessThan(Carbon::now())) {
                throw new Exception("行程已过期", 404);
            }
        }
    }

    /**
     * @throws Exception
     */
    private function getValidInviteInfo(array $data)
    {
        $this->validInitData($data);
        // 验证同一趟车次 未过期的数据
        return $this->where('user_id', $data['user_id'])
                    ->where('train_code', $data['train_code'])
                    ->where('from_station_code', $data['from_station_code'])
                    ->where('to_station_code', $data['to_station_code'])
                    ->where('train_date', $data['train_date'])
                    ->where('created_at', '>', Carbon::now()->subSeconds(self::EXPIRED_TIMES)->toDateTimeString())
                    ->orderByDesc('invite_id')
                    ->first();
    }

    /**
     * 用户是否是邀请者
     *
     * @param string      $user_id
     * @param Invite|null $invite
     *
     * @return int
     */
    public function is_inviter(string $user_id, ?Invite $invite = null): int
    {
        if ($invite) {
            return $invite->inviter_id() == $user_id ? 1 : 0;
        }
        return $this->inviter_id() == $user_id ? 1 : 0;
    }

    /**
     * @throws Exception
     */
    public function train_info(): array
    {
        $train_info = new TrainInfo();
        /**
         * 'train_code',
         * 'from_station_code',
         * 'to_station_code',
         * 'train_date',
         */
        $train_info->setTrainCode($this->train_code);
        $train_info->setFromStationCode($this->from_station_code);
        $train_info->setToStationCode($this->to_station_code);
        $train_info->setDepartAt($this->train_date);
        return $train_info->getTrainInfo();
    }
}
